package maquinaP;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Stack;

/**
 * Clase que implementa la Maquina P.
 * @author Miguel Angel Diaz.
 * @author David Martin.
 * @version 2.0
 */
public class MaquinaP {
	
	/** Pila. */
	private Stack<Integer> pila;
	
	/** Memoria de instrucciones. */
	private ArrayList<String> mInstr;
	
	/** Memoria de datos. */
	private int[] mDatos;
	
	/** Contador de programa. */
	private int cp;
	
	/** Registro de estado: 'r' ejecutando; 's' parada y 'e' error. */
	private char regF;
	
	/** Registro H: apunta a la primera posicion libre de la memoria dinamica. */
	private int regH;
	
	/** 
	 * Registro B: base para las direcciones relativas del registro de activacion. Apunta a la base del registro de activacion.
	 * activo: el que esta en la cima de la pila.
	 */
	private int regB;
	
	/** Registro C: apunta a la cima de la pila de registros de activacion. */
	private int regC;
	
	/** Mensaje de informacion. */
	private String info;
	
	/** Inicio de la zona de memoria dinamica dentro de la zona de memoria de datos. */
	public static int INICIO_MEM_DINAMICA = 500;
	
	/**
	 * Inicializa todos los atributos del objeto.
	 */
	public MaquinaP(){
		reinicia();
		mInstr = new ArrayList<String>();
	}
	
	/**
	 * Inicializa todos los atributos del objeto menos la memoria de instrucciones, que no la cambia.
	 */
	public void reinicia(){
		pila = new Stack<Integer>();
		mDatos = new int[1000];
		cp = 0;
		regF = 'r';
		regH = INICIO_MEM_DINAMICA;
	    regB = 0;
		regC = 0;
		info = "";
	}
	
	/**
	 * Consulta el registro B.
	 * @return B registro B: base del registro de activacion activo.
	 */
	public int getB() {
		return regB;
	}

	/**
	 * Modifica el registro B.
	 * @param B Nuevo valor para el registro B.
	 */
	public void setB(int B) {
		regB = B;
	}

	/**
	 * Consulta el registro C.
	 * @return registro C: Cima de la pila de registros de activacion.
	 */
	public int getC() {
		return regC;
	}

	/**
	 * Modifica el registro C.
	 * @param C Nuevo valor para el registro C.
	 */
	public void setC(int C) {
		regB = C;
	}

	/**
	 * Consulta el registro de estado.
	 * @return Caracter con el estado.
	 */
	public char getF() {
		return regF;
	}

	/**
	 * Modifica el registro de estado.
 	 * @param f Nuevo estado.
	 */
	public void setF(char f) {
		this.regF = f;
	}

	/**
	 * Consulta el registro H.
	 * @return Registro H: Primera posicion libre de memoria dinamica.
	 */
	public int getH() {
		return regH;
	}

	/**
	 * Modifica el registro H.
	 * @param H Nuevo valor para el registro H.
	 */
	public void setH(int H) {
		regH = H;
	}

	/**
	 * Consulta el contador de programa.
	 * @return Contador de programa: Siguente instruccion a ejecutar.
	 */
	public int getCP() {
		return cp;
	}

	/**
	 * Modifica el contador de programa.
	 * @param cp Nuevo valor para el contador de programa.
	 */
	public void setCP(int cp) {
		this.cp = cp;
	}

	/**
	 * Consulta la cima de la pila.
	 * @return cima de la pila. Si esta vacia pasa a estado de error y devuelve <i>Integer.MIN_VALUE</i>.
	 */
	public int getCimaPila() {
		if (!pila.empty())
			return pila.peek();
		else {
			setError("Error al consultar la cima y estar la pila vacia.");
			return Integer.MIN_VALUE;
		}
	}

	/**
	 * Consulta la pila de operandos.
	 * @return La pila de operandos.
	 */
	public Stack<Integer> getPila() {
		return pila;
	}

	/**
	 * Consulta una posicion de memoria de datos.
	 * @param pos Posicion a consultar.
	 * @return Contenido de la posicion pos. Si pos esta fuera del dominio cambia el estado a error y devuelve 
	 * <i>Integer.MIN_VALUE</i>.
	 */
	public int getPosMDatos(int pos) {
		if (pos>=0 && pos<mDatos.length)
			return mDatos[pos];
		else {
			setError("Error al intentar consultar una posicion de memoria fuera del dominio.");
			return Integer.MIN_VALUE;
		}
	}

	/**
	 * Consulta la memoria de datos.
	 * @return Contenido memoria de datos.
	 */
	public int[] getMDatos() {
		return mDatos;
	}

	/**
	 * Consulta una posicion de memoria de instrucciones.
	 * @param pos Posicion a consultar.
	 * @return Contenido de la posicion pos. Si no existe pasa a estado de error y devuelve <i>null</i>.
	 */
	public String getPosMInstr(int pos) {
		if (pos>=0 && pos<mInstr.size())
			return mInstr.get(pos);
		else {
			setError("Error al intentar acceder a una instruccion inexistente.");
			return null;
		}
	}

	/**
	 * Consulta la memoria de instrucciones.
	 * @return Memoria de instrucciones.
	 */
	public ArrayList<String> getMInstr() {
		return mInstr;
	}

	/**
	 * Cambia la memoria de instrucciones.
	 * @param instrucciones Nueva memoria de instrucciones.
	 */
	public void setPrograma(ArrayList<String> instrucciones){
		mInstr = instrucciones;
	}
	
	/**
	 * Lee un fichero escrito en codigo objeto.
	 * Si no se puede leer, informa y pasa a estado de error.
	 * @param rutaFichero Ruta del archivo a leer.
	 */
	public void cargarPrograma(String rutaFichero){
		File fich= new File(rutaFichero);
		FileReader fichero;
		try{
			fichero = new FileReader(fich);
			mInstr = new ArrayList<String>();
			BufferedReader entrada = null;
		    try {
		      entrada = new BufferedReader(fichero);
		      String linea = null;
		      while ((linea = entrada.readLine()) != null){
		    	  String leido = linea.trim();
		    	  leeLinea(leido);
		      }
		    }
		    catch (Exception ex) {
			      setError("Error al leer el archivo: " + rutaFichero);
			}
		}
		catch(java.io.FileNotFoundException e) {
			setError("Fichero no valido: " + rutaFichero);
		}
	}
	
	/**
	 * Procesa una linea del fichero fuente descartando comentarios (todo lo que siga a "//").
	 * @param linea Texto de la linea.
	 */
	private void leeLinea(String linea){
		String se = linea.replaceAll(" ", "").replaceAll("\t", "");
		if (!se.startsWith("//") && se.length()!=0){
			mInstr.add(se.substring(0,se.indexOf(';')));
			if (se.indexOf(';')+1 < se.length())
				leeLinea(se.substring(se.indexOf(';')+1, se.length()));
		}
	}
	
	/**
	 * Consulta si la Maquina P tiene un programa cargado en memoria.
	 * @return <i>true</i> si tiene un programa cargado.
	 */
	public boolean programaCargado(){
		return mInstr.size() > 0;
	}
	
	/**
	 * Recupera el parametro de la instruccion.
	 * @param instr Instruccion.
	 * @return Lista de parametros.
	 */
	private ArrayList<Integer> getParams(String instr){
		ArrayList<Integer> params = new ArrayList<Integer>();
		int pAp = instr.indexOf('(');
		int pCi = instr.indexOf(')');
		int i = pAp;
		int ini = pAp;
		while (i < pCi){
			if (instr.charAt(i+1)==',' || instr.charAt(i+1)==')'){
				params.add(Integer.valueOf(instr.substring(ini+1, i+1)));
				ini = i+1;
			}
			i++;
		}
		return params;
	}
	
	/**
	 * Ejecuta la siguiente instruccion si se puede.
	 * Si la instruccion esta indefinida, se detiene la ejecucion.
	 */
	public void ejecutaInstruccion(){
		if (puedeContinuar()){
			String sig = mInstr.get(cp);
			ArrayList<Integer> params = getParams(sig);
			try {
				if (sig.startsWith("apila(") && sig.endsWith(")"))
			    	apila(params.get(0));
			    else if (sig.startsWith("apila-dir(") && sig.endsWith(")"))
			    	apila_dir(params.get(0), params.get(1));
			    else if (sig.equals("apila-ind"))
			    	apila_ind();
			    else if (sig.equals("desapila"))
			    	desapila();
			    else if (sig.startsWith("desapila-dir(") && sig.endsWith(")"))
			    	desapila_dir(params.get(0), params.get(1));
			    else if (sig.equals("desapila-ind"))
			    	desapila_ind();
			    else if (sig.startsWith("ir-a(") && sig.endsWith(")"))
			    	ir_a(params.get(0));
			    else if (sig.startsWith("ir-f(") && sig.endsWith(")"))
			    	ir_f(params.get(0));
			    else if (sig.startsWith("ir-v(") && sig.endsWith(")"))
			    	ir_v(params.get(0));
			    else if (sig.startsWith("llamar(") && sig.endsWith(")"))
			    	llamar(params.get(0), params.get(1));
			    else if (sig.equals("retorno"))
			    	retorno();
			    else if (sig.equals("suma"))
			    	suma();
			    else if (sig.equals("resta"))
			    	resta();
			    else if (sig.equals("mult"))
			    	mult();
			    else if (sig.equals("div"))
			    	div();
			    else if (sig.equals("mod"))
			    	mod();
			    else if (sig.equals("inverso"))
			    	inverso();
			    else if (sig.equals("or"))
			    	or();
			    else if (sig.equals("and"))
			    	and();
			    else if (sig.equals("igual"))
			    	igual();
			    else if (sig.equals("dist"))
			    	dist();
			    else if (sig.equals("menor"))
			    	menor();
			    else if (sig.equals("menigual"))
			    	menigual();
			    else if (sig.equals("mayor"))
			    	mayor();
			    else if (sig.equals("mayigual"))
			    	mayigual();
			    else if (sig.equals("neg"))
			    	neg();
			    else if (sig.equals("apilaH"))
			    	apilaH();
			    else if (sig.startsWith("incrementaH(") && sig.endsWith(")"))
			    	incrementaH(params.get(0));
			    else if (sig.startsWith("incrementaC(") && sig.endsWith(")"))
			    	incrementaC(params.get(0));
			    else if (sig.equals("copia"))
			    	copia();
			    else if (sig.equals("imprime"))
			    	imprime();
			    else if (sig.equals("alto"))
			    	setInfo("Ejecucion finalizada");
			    else
					setInfo("Operacion desconocida: " + sig);
			}
			catch (IndexOutOfBoundsException e){
				setError("Parametros incorrectos: " + cp + "@ " + sig);
			}
		}
	}

	/**
	 * Ejecuta el programa desde la posicion actual hasta el final si no se produce ningun error antes.
	 */
	public void ejecutaPrograma(){
		while (puedeContinuar())
			ejecutaInstruccion();
	}
	
	/**
	 * Consulta si se puede continuar con la ejecucion: si el estado es ejecutando ('r') y hay instrucciones por ejecutar.
	 * @return <i>true</i> si se puede seguir.
	 */
	private boolean puedeContinuar(){
		if ((regF == 'r') && (cp < mInstr.size()))
			return true;
		else { 
			regF = 's';
			return false;
		}
	}
	
	/**
	 * Consulta el mensaje guardado.
	 * @return Mensaje guardado.
	 */
	public String getInfo(){
		return info;
	}
	
	/**
	 * Modifica el estado a error y guarda el mensaje.
	 * @param error Mensaje de error.
	 */
	public void setError(String error){
		regF = 'e';
		this.info = "ERROR: " + error;
	}
	
	/**
	 * Modifica el estado a parada y guarda el mensaje.
	 * @param info Mensaje de informacion.
	 */
	public void setInfo(String info){
		regF = 's';
		this.info = "INFO: " + info;
	}
	
//-----------------------------------------------------------------------------
//----   OPERACIONES SOBRE LA PILA
	
	/**
	 * Apila un numero.
	 * @param n Valor a apilar.
	 */
	private void apila(int n){
		pila.push(n);
		cp++;
	}
	
	/**
	 * Apila la posicion 'base + dir' desde la memoria de datos.
	 * @param nv Diferencia de niveles na - nd.
	 * @param dir Direccion relativa.
	 */
	private void apila_dir(int nv, int dir){
		int base = regB;
		while (nv > 0){
			base = mDatos[base];
			nv = nv - 1;
		}
		try {
			pila.push(mDatos[base + dir]);
			cp++;
		}
		catch(IndexOutOfBoundsException e){
			setError("Posicion de memoria " + (base+dir) + " no valida!!!");
		}
	}
	
	/**
	 * Desapila dir y nv. Apila el contenido de la direccion de memoria 'base + dir'.
	 */
	private void apila_ind(){
		if (pila.size() >= 2){
			int base = regB;
			int dir = pila.pop();
			int nv = pila.pop();
			while (nv > 0){
				base = mDatos[base];
				nv--;
			}
			try {
				pila.push(mDatos[base+dir]);
				cp++;
			}
			catch(IndexOutOfBoundsException e){
				setError("Posicion de memoria " + (base+dir) + " no valida!!!");
			}
		}
		else
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Metodo que desapila la cima de la pila.
	 */
	private void desapila(){
		if (!pila.empty()){
			pila.pop();
			cp++;
		}
		else
			setError("Pila vacia!!!");
	}
	
	/**
	 * Guarda la cima de la pila en la posicion de memoria 'base + dir' y desapila.
	 * @param nv Diferencia de niveles na - nd.
	 * @param dir Direccion relativa.
	 */
	private void desapila_dir(int nv, int dir){
		if (!pila.empty()){
			int base = regB;
			while (nv > 0){
				base = mDatos[base];
				nv = nv - 1;
			}
			try {
				mDatos[base + dir] = pila.pop();
				cp++;
			}
			catch(IndexOutOfBoundsException e){
				setError("Posicion de memoria " + (base+dir) + " no valida!!!");
			}
		}
		else
			setError("Pila vacia!!!");
	}
	
	/**
	 * Desapila v, dir y nv. En la direccion de memoria dir relativa al nivel guarda v.
	 */
	private void desapila_ind(){
		if (pila.size() >= 3){
			int base = regB;
			int v = pila.pop();
			int dir = pila.pop();
			int nv = pila.pop();
			if (dir < 3)
				setError("Acceso a memoria restringida");
			else {
				while (nv>0){
					base = mDatos[base];
					nv--;
				}
				try {
					mDatos[base + dir] = v;
					cp++;
				}
				catch(IndexOutOfBoundsException e){
					setError("Posicion de memoria " + (base+dir) + " no valida!!!");
				}
			}
		}
		else
			setError("Faltan operandos en la pila.");
	}
	
	/**
	 * Apila en la pila el contenido del registro H.
	 */
	private void apilaH(){
		pila.push(regH);
		cp++;
	}

//-----------------------------------------------------------------------------
//----   OPERACIONES DE CONTROL
	
	/**
	 * Salto incondicional a la instruccion d.
	 * @param d Direccion destino del salto.
	 */
	private void ir_a(int d){
		if (d < 0 || d >= mInstr.size())
			setError("Direccion de instruccion no valida");
		else 
			cp = d;
	}
	
	/**
	 * Desapila el valor de la cima de la pila. Si es 0 (falso), salta a la instruccion i-esima. En otro caso,
	 * sigue la ejecucion en secuencia.
	 * @param d Direccion destino del salto.
	 */
	private void ir_f(int d){
		if (!pila.empty()){
			if (d>=0 && d<mInstr.size()){
				int cond = pila.pop();
				if (cond == 0)
					ir_a(d);
				else
					cp++;
			}
			else
				setError("Direccion de instruccion no valida");
		}
		else
			setError("Pila vacia!!!");
	}
	
	/**
	 * Desapila el valor de la cima de la pila. Si es distinto de 0 (verdadero), salta a la instruccion i-esima. 
	 * En otro caso, sigue la ejecucion en secuencia.
	 * @param d Direccion destino del salto.
	 */
	private void ir_v(int d){
		if (!pila.empty()){
			if (d>=0 && d<mInstr.size()){
				int cond = pila.pop();
				if (cond != 0)
					ir_a(d);
				else
					cp++;
			}
			else
				setError("Direccion de instruccion no valida");
		}
		else
			setError("Pila vacia!!!");
	}
	
	/**
	 * Guarda el estado y pasa a ejecutar una funcion.
	 * @param dif Diferencia de niveles.
	 * @param etiq Direccion de comienzo de la funcion a ejecutar.
	 */
	private void llamar(int dif, int etiq){
		try {
			mDatos[regC+2] = cp+1;      // (IS)
			mDatos[regC+1] = regB;      // (ED)
			int base = regB;
			while (dif>0){
				base = mDatos[base];
				dif = dif - 1;
			}
			mDatos[regC] = base;        // (EE)
			regB = regC;                // base del nuevo reg. de activacion
			cp = etiq;
		}
		catch (IndexOutOfBoundsException e){
			setError("Direccion de memoria no valida");
		}
	}
	
	/**
	 * Metodo que establece el estado a antes de llamar a la funcion actual.
	 */
	private void retorno(){
		try {
			regC = regB;
			regB = mDatos[regC+1];
			cp = mDatos[regC+2];
		}
		catch (IndexOutOfBoundsException e){
			setError("Direccion de memoria no valida");
		}
	}
		
//-----------------------------------------------------------------------------
//----   OPERACIONES ARITMETICAS
		
	/**
	 * Suma las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void suma(){
		if (pila.size() >= 2){
			int op1 = pila.pop();      
			int op0 = pila.pop();
			pila.push(op0 + op1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Resta las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void resta(){
		if (pila.size() >= 2){
			int op1 = pila.pop();      
			int op0 = pila.pop();
			pila.push(op0 - op1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Multiplica las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void mult(){
		if (pila.size() >= 2){
			int op1 = pila.pop();       
			int op0 = pila.pop();
			pila.push(op0 * op1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Divide las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void div(){
		if (pila.size() >= 2){
			int op1 = pila.pop();       
			int op0 = pila.pop();
			if (op1 != 0){
				pila.push(op0 / op1);
				cp++;
			}
			else
				setError("Division por 0!!!");
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Calcula el resto de la division entera de las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void mod(){
		if (pila.size() >= 2){
			int op1 = pila.pop();       
			int op0 = pila.pop();
			if (op1 != 0){
				pila.push(op0 % op1);
				cp++;
			}
			else
				setError("Division por 0!!!");
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Calcula el inverso de un numero.
	 */
	private void inverso(){
		if (!pila.empty()){
			int op = pila.peek();   
			if (op != 0){
				pila.pop();
				pila.push(-op);
			}
			cp++;
		}
		else 
			setError("Falta operando en la pila");
	}
	
//-----------------------------------------------------------------------------
//----   OPERACIONES DE LOGICAS Y COMPARACIONES
		
	/**
	 * Calcula la or de las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void or(){
		if (pila.size() >= 2){
			int op1 = pila.pop();       
			int op0 = pila.pop();
			if (op1 == 0 && op0 == 0)
			    pila.push(0);
			else
				pila.push(1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Calcula la and de las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void and(){
		if (pila.size() >= 2){
			int op1 = pila.pop();       
			int op0 = pila.pop();
			if (op1 == 1 && op0 == 1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
		
	/**
	 * Mira si son iguales las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void igual(){
		if (pila.size() >= 2){
			int op1 = pila.pop();     
			int op0 = pila.pop();
			if (op1 == op0)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Mira si son distintos las dos posiciones mas altas de la pila.
	 * Primer operando subcima, segundo operando cima.
	 */
	private void dist(){
		if (pila.size() >= 2){
			int op1 = pila.pop();      
			int op0 = pila.pop();
			if (op1 != op0)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}

	/**
	 * Calcula si la subcima es menor que la cima.
	 */
	private void menor(){
		if (pila.size() >= 2){
			int op1 = pila.pop();      
			int op0 = pila.pop();
			if (op0 < op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Calcula si la subcima es menor o igual que la cima.
	 */
	private void menigual(){
		if (pila.size() >= 2){
			int op1 = pila.pop();      
			int op0 = pila.pop();
			if (op0 <= op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Calcula si la subcima es mayor que la cima.
	 */
	private void mayor(){
		if (pila.size() >= 2){
			int op1 = pila.pop();
			int op0 = pila.pop();
			if (op0 > op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Calcula si la subcima es mayor o igual que la cima.
	 */
	private void mayigual(){
		if (pila.size() >= 2){
			int op1 = pila.pop();       
			int op0 = pila.pop();
			if (op0 >= op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * Calcula la negacion de la cima.
	 */
	private void neg(){
		if (!pila.empty()){
			int op = pila.pop();     
			if (op == 0)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
//-----------------------------------------------------------------------------
//----   OTRAS

	/**
	 * Incrementa en n posiciones el registro H.
	 * @param n Numero de posiciones a incrementar.
	 */
	private void incrementaH(int n){
		regH += n;
		if (regH < mDatos.length)
			cp++;
		else
			setError("Memoria dinamica insuficiente. Maximo:" + (mDatos.length-1) + ", Actual:" + regH);
	}
	
	/**
	 * Incrementa el C: el valor de la cima del area de datos.
	 * @param n Numero de posiciones a incrementar.
	 */
	private void incrementaC(int n){
		regC += n;
		if (regC < INICIO_MEM_DINAMICA)
			cp++;
		else
			setError("Memoria de datos insuficiente. Maximo:" + INICIO_MEM_DINAMICA + ", Actual:" + regC);
	}
	
	/**
	 * Duplica el valor de la cima de la pila.
	 */
	private void copia(){
		if (!pila.empty()){
			pila.push(pila.peek());
			cp++;
		}
		else
			setError("Pila vacia!!!");
	}
	
	/**
	 * Muestra la cima de la pila, eliminando dicha posicion.
	 */
	private void imprime(){
		if (!pila.empty()){
			info = "Salida del programa: " + pila.pop();
			cp++;
		}
		else
			setError("Pila vacia!!!");
	}
	
}
